package elephant.servers.gameserver.session;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import elephant.Container;
import elephant.core.ContainerThreadFactory;
import elephant.servers.gameserver.core.GameRoom;

/**
 * 
 * @author icecooly
 *
 */
public class GameSessionManager {
	private static Logger logger=LoggerFactory.getLogger(GameSessionManager.class);
	private Map<Integer,GameSession>sessionMap;
	private Map<String,GameSession>principalMap;
	private Map<String, GameRoom>roomMap;
	private List<SessionLifecycleListener>lifecycleListeners;
	//
	private int sessionTimeout=5*60;
	private AtomicInteger sessionId;
	private int maxSessionCount=Integer.MAX_VALUE;
	//
	public GameSessionManager() {
		sessionMap=new ConcurrentHashMap<>();
		principalMap=new ConcurrentHashMap<>();
		roomMap=new ConcurrentHashMap<>();
		sessionId=new AtomicInteger();
		lifecycleListeners=Collections.synchronizedList(
				new LinkedList<SessionLifecycleListener>());
	}
	//
	public void startSessionChecker(){
		ScheduledExecutorService es=Executors.newScheduledThreadPool(1,
					new ContainerThreadFactory("SessionChecker"));
		es.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				long currentTime = System.currentTimeMillis();
				for(GameSession session:sessionMap.values()){
					try{
						checkPrincipal(currentTime,session);	
						checkSessionTimeout(currentTime, session);
					}catch(Exception e){
						logger.error(e.getMessage(),e);
					}
				}
			}
		}, 30,30,TimeUnit.SECONDS);
	}
	
	/***
	 * 检查不连接的session
	 * @param currentTime
	 * @param session
	 */
	private void checkPrincipal(long currentTime,GameSession session) {
		if (session.getPrincipal() == null) {
			if ((currentTime - session.getLastAccessTime() )> 60 * 1000) {
				session.kick("can not find principal");
			}
		}
	}
	/**
	 * 服务器会根据sessionTimeout时间保留掉线的session，当session掉线时间超过timeout时间
	 * 的时候就彻底删除这些session
	 */
	private void checkSessionTimeout(long currentTime,GameSession session){
		if(!session.isActive()){
			if((currentTime-session.getLastAccessTime())>sessionTimeout*1000){
				sessionDestroyed(session);
			}
		}
	}
	/**
	 * 
	 * @param l
	 */
	public void registerSessionLifecycleListener(SessionLifecycleListener l){
		lifecycleListeners.add(l);
	}
	/**
	 * 
	 * @param l
	 */
	public void removeSessionLifecycleListener(SessionLifecycleListener l){
		lifecycleListeners.remove(l);
	}
	//
	public void sessionCreated(GameSession session){
		synchronized (session) {
			sessionCreated0(session);
			//
			if(lifecycleListeners.size()>0){
				Container.get().executeThreadWorker(new Runnable() {
					public void run() {
						lifecycleListeners.forEach(listener->{
							listener.onSessionCreated(session);
						});
					}
				});
			}
		}
	}
	//
	private void sessionCreated0(GameSession session){
		session.setActive(true);
		//
		int nextSessionId=sessionId.incrementAndGet();
		session.setId(nextSessionId);
		if(logger.isDebugEnabled()){
			logger.debug("session created:"+
					session.getId()+"/"+
					session.getRemoteHost()+":"+
					session.getRemotePort());
		}
		//
		sessionMap.put(session.getId(),session);
	}
	
	/**
	 * session socket connection disconnected
	 * @param session
	 */
	public void sessionDisconnected(GameSession session){
		synchronized (session) {
			sessionDisconnected0(session);
			//
			if(lifecycleListeners.size()>0){
				Container.get().executeThreadWorker(new Runnable() {
					public void run() {
						lifecycleListeners.forEach(listener->{
							listener.onSessionDisconnected(session);
						});
					}
				});
			}
		}
	}
	/**
	 * 
	 * @param session
	 */
	private void sessionDisconnected0(GameSession session){
		//没有principal的session 关闭连接以后直接踢掉
		if(session.getPrincipal()==null){
			sessionMap.remove(session.getId());
			return;
		}
		session.setActive(false);
		session.updateLastAccessTime();
		if(logger.isDebugEnabled()){
			logger.debug("session disconnected:"+
					session.getId()+"/"+
					session.getRemoteHost()+":"+
					session.getRemotePort());
		}
	}
	//
	public void sessionDestroyed(GameSession session){
		synchronized (session) {
			sessionDestroyed0(session);
			//
			if(lifecycleListeners.size()>0){
				Container.get().executeThreadWorker(new Runnable() {
					public void run() {
						lifecycleListeners.forEach(listener->{
							listener.onSessionDestroyed(session);
						});
					}
				});
			}
		}
	}
	//
	private void sessionDestroyed0(GameSession session){
		if(logger.isDebugEnabled()){
			logger.debug("session destroyed:"+
					session.getId()+"/"+
					session.getRemoteHost()+":"+
					session.getRemotePort());
		}
		sessionMap.remove(session.getId());
		//
		if(session.getPrincipal()!=null){
			principalMap.remove(session.getPrincipal());
		}
		//auto remove disconnect session from room
		for(String roomId:session.getJoinRooms()){
			GameRoom room=getRoom(roomId);
			if(room!=null){
				session.leaveRoom(room);
			}
		}
	}
	
	//--------------------------------------------------------------------------
	/**广播全部的session*/
	public void broadcast(String serviceId,Object payload){
		for(GameSession session:sessionMap.values()){
			if(session.isActive()){
				session.push(serviceId, payload);
			}
		}
	}
	/**广播全部的session*/
	public void broadcast(String serviceId,Object payload,int excludeSessionId){
		sessionMap.values().forEach(session->{
			if(session.isActive()){
				if(session.getId()!=excludeSessionId){
					session.push(serviceId, payload);
				}
			}
		});
	}
	//--------------------------------------------------------------------------
	/**
	 * 创建房间
	 * @param id
	 * @return
	 */
	public GameRoom createRoom(String id){
		if(logger.isDebugEnabled()){
			logger.debug("create room:"+id);
		}
		if(id==null||id.length()==0){
			throw new IllegalArgumentException("id can not be null.");
		}
		GameRoom room=roomMap.get(id);
		if(room!=null){
			return room;
		}
		room=new GameRoom(id,this);
		roomMap.put(id, room);
		return room;
	}
	/**
	 * 
	 * @param id
	 * @return
	 */
	public GameRoom getRoom(String id){
		return roomMap.get(id);
	}
	/**
	 * return all rooms
	 * @return
	 */
	public List<GameRoom> getRooms(){
		List<GameRoom>list=new ArrayList<>(roomMap.values());
		return list;
	}
	//
	public int getRoomCount(){
		return roomMap.size();
	}
	//
	public GameRoom removeRoom(String id){
		if(logger.isDebugEnabled()){
			logger.debug("remove room:"+id);
		}
		return roomMap.remove(id);
	}
	//--------------------------------------------------------------------------
	/**
	 * 
	 */
	public GameSession getSessionByPrincipal(String principal){
		if(principal==null){
			throw new IllegalArgumentException("principal can not be null.");
		}
		return principalMap.get(principal);
	}
	/**
	 * 
	 */
	public GameSession getSessionById(int id){
		return sessionMap.get(id);
	}
	/**
	 * setup session principal info,user agent is an option but recommend
	 */
	public void setPrincipal(GameSession s,String principal,String userAgent){
		synchronized (s) {
			setPrincipal0(s,principal,userAgent);
		}
	}
	//
	private void setPrincipal0(GameSession session,String principal,String userAgent){
		if(principal==null){
			throw new IllegalArgumentException("principal can not be null.");
		}
		if(session.getPrincipal()!=null){
			throw new IllegalArgumentException("principal already set to:"+
					session.getPrincipal());
		}
		session.setPrincipal(principal);
		session.setUserAgent(userAgent);
		GameSession oldSession=principalMap.get(principal);
		if(logger.isDebugEnabled()){
			logger.debug("setPrincipal {}",principal);
		}
		if(oldSession==session){
			//same session,ignore it
			return ;
		}
		if(oldSession!=null){
			principalMap.remove(principal);
			sessionMap.remove(oldSession.getId());
			if(oldSession.isActive()){
				oldSession.setPrincipal(null);
				oldSession.kick("kicked by same principal.");		
			}
			//同名用户登录成功以后新session使用老用户的缓存信息
			session.setUserObject(oldSession.getUserObject());
		}
		//NOTE 在old session存在的情况下，新的session会直接替换掉老的session 老的session
		//不会触发sessionDestroyed事件，这么做的目的是防止出现新用户登录提到同名老用户
		//业务层收到sessionDestroyed事件认为用户下线，造成困扰
		//
		principalMap.put(principal,session);
	}
	
	
	public int getMaxSessionCount() {
		return maxSessionCount;
	}
	/**
	 * @param maxSessionCount the maxSessionCount to set
	 */
	public void setMaxSessionCount(int maxSessionCount) {
		this.maxSessionCount = maxSessionCount;
	}
	
	public int getSessionCount(){
		return sessionMap.size();
	}
	
	public List<GameSession>getSessions(){
		return new ArrayList<>(sessionMap.values());
	}
	
	public int getSessionTimeout() {
		return sessionTimeout;
	}
	
	public void setSessionTimeout(int sessionTimeout) {
		this.sessionTimeout = sessionTimeout;
	}
	
}
